﻿namespace GenericEnum
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Reflection;

	/// <summary>
	/// Represents generic class of enumeration.
	/// </summary>
	public class Enumeration<T> where T : struct, IComparable, IFormattable, IConvertible
	{
		#region constructors

		static Enumeration()
		{
			if (!typeof(T).IsEnum)
			{
				throw new Exception(string.Format("Type {0} is not an enumeration.", typeof(T)));
			}

			Array valueSet = Enum.GetValues(typeof(T));
			Description = new Dictionary<T, string>(valueSet.Length);

			// Description initialization.
			// If enum members have not any description, default values will be used.
			foreach (T enumValue in valueSet)
			{
				Description[enumValue] = GetDescription(enumValue);
			}
		}

		/// <summary>
		/// Sets enumeration value.
		/// </summary>
		/// <param name="enumValue">Enumeration value.</param>
		public Enumeration(T enumValue)
		{
			Value = enumValue;
		}

		/// <summary>
		/// Sets enumeration value.
		/// </summary>
		/// <param name="enumString">Enumeration case sensitive value.</param>
		/// <exception cref="ArgumentException">Invalid string value.</exception>
		public Enumeration(string enumString)
		{
			T enumValue;
			if (Enum.TryParse<T>(enumString, out enumValue))
			{
				Value = enumValue;
			}
			else
			{
				throw new ArgumentException(string.Format("{0} does not contain value \"{1}\".", typeof(T).FullName, enumString));
			}
		}

		#endregion // constructors

		#region properties

		/// <summary>
		/// Represents dictionary of an enumeration description.
		/// </summary>
		public static Dictionary<T, string> Description { get; private set; }

		/// <summary>
		/// Public property 
		/// </summary>
		public T Value { get; set; }

		#endregion // properties

		#region overloading

		/// <summary>
		/// Implicit operator of type conversion.
		/// </summary>
		/// <param name="enumValue">Object to convert.</param>
		/// <returns>Enum value of the current object.</returns>
		public static implicit operator T(Enumeration<T> enumValue)
		{
			return enumValue.Value;
		}

		/// <summary>
		/// Implicit operator of type conversion.
		/// </summary>
		/// <param name="enumValue">Object to convert.</param>
		/// <returns>String value of the current object.</returns>
		public static implicit operator string(Enumeration<T> enumValue)
		{
			return Description[enumValue.Value];
		}

		#endregion // overloading

		#region overriding

		/// <summary>
		/// Converts current object to string.
		/// </summary>
		/// <returns>String value of the current object.</returns>
		/// <exception cref="Exception">Exception if string dictionary is in incorrect state.</exception>
		public override string ToString()
		{
			if (Description.ContainsKey(Value))
			{
				return Description[Value];
			}

			throw new Exception(string.Format("Invalid enumeration value {0}.", Value));
		}

		/// <summary>
		/// Compares the current object with the <paramref name="obj"/>.
		/// </summary>
		/// <param name="obj">Object to compare with.</param>
		/// <returns>True if objects are equal, false otherwise.</returns>
		public override bool Equals(object obj)
		{
			Enumeration<T> enumObject = obj as Enumeration<T>;
			if (enumObject == null)
			{
				return false;
			}

			return enumObject.Value.Equals(Value);
		}

		/// <summary>
		/// Gets enum hash code.
		/// </summary>
		/// <returns>Hash code.</returns>
		public override int GetHashCode()
		{
			return Value.GetHashCode();
		}

		#endregion // overriding

		#region implementation

		public static string GetDescription(T value)
		{
			Type type = value.GetType();
			FieldInfo fi = type.GetField(value.ToString());
			DescriptionAttribute[] description = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
			if (description.Length > 0)
			{
				return description[0].Description;
			}

			// Default representation of the enum value.
			return value.ToString();
		}

		#endregion // implementation
	}
}